require "./classes"

class BinaryStream
	def initialize (file)
		@file = file
		
		File.open(file, "rb") { |desc|
			@data = desc.read
		}
		
		@byte = 0
	end
	
	# Move the internal index by size bytes
	# If the index is negative or greater then the size of the stream's data, it will clamp to 0 or the last byte respectively 
	def move (size)
		@byte += size
		
		if @byte < 0
			@byte = 0
		elsif @byte >= @data.size
			@byte = @data.size - 1
		end
	end
	
	# Skip size bytes
	# If a call results in the internal index being greater then the stream's data size, it is clamped to the last byte
	def skip (size)
		@byte += size
		
		if @byte >= @data.size
			@byte = @data.size - 1
		end
	end
	
	# Returns the number of bytes remaining until the end of the stream
	def remaining ()
		return @data.size - @byte
	end
	
	# Reads size bytes and return them as string
	def read (size)
		if @data.size - @byte >= size
			data = @data[@byte...@byte + size]
			@byte += size
			
			return data
		else
			return nil
		end
	end
	
	# Reads a null terminated C string
	def readCStr (size)
		read(size).unpack("A*")[0]
	end
	
	# Reads 1 byte and parse it as an a signed integer
	def readI8 ()
		return read(1).unpack("c")[0]
	end
	
	# Reads 2 bytes and parse them as an a signed integer
	def readI16 ()
		return read(2).unpack("s")[0]
	end
	
	# Reads 4 bytes and parse them as an a signed integer
	def readI32 ()
		return read(4).unpack("l")[0]
	end
	
	# Reads 8 bytes and parse them as an a signed integer
	def readI64
		return read(8).unpack("q")[0]
	end
	
	# Reads 1 byte and parse it as an unsigned integer
	def readUI8 ()
		return read(1).unpack("C")[0]
	end
	
	# Reads 2 bytes and parse them as an unsigned integer
	def readUI16 ()
		return read(2).unpack("S")[0]
	end
	
	# Reads 4 bytes and parse them as an unsigned integer
	def readUI32 ()
		return read(4).unpack("L")[0]
	end
	
	# Reads 8 bytes and parse them as an unsigned integer
	def readUI64 ()
		return read(8).unpack("Q")[0]
	end
	
	# Reads 4 bytes and parse them as a floating point number
	def readF ()
		return read(4).unpack("f")[0]
	end
	
	# Reads 8 bytes and parse them as a double precision floating point number
	def readD ()
		return read(8).unpack("d")[0]
	end
	
	# Reads 8 bytes and parse them as an array of 2 signed integer numbers
	def readIV2 ()
		return read(8).unpack("l*")
	end
	
	# Reads 12 bytes and parse them as an array of 3 signed integer numbers
	def readIV3 ()
		return read(12).unpack("l*")
	end
	
	# Reads 16 bytes and parse them as an array of 4 signed integer numbers
	def readIV4 ()
		return read(16).unpack("l*")
	end
	
	# Reads 8 bytes and parse them as an array of 2 floating point numbers
	def readFV2 ()
		return read(8).unpack("f*")
	end
	
	# Reads 12 bytes and parse them as an array of 3 floating point numbers
	def readFV3 ()
		return read(12).unpack("f*")
	end
	
	# Reads 16 bytes and parse them as an array of 4 floating point numbers
	def readFV4 ()
		return read(16).unpack("f*")
	end
	
	# Reads an MDX chunk consisting of a 4-byte sized token and a 4-byte unsigned integer chunk size
	def readChunk ()
		if remaining() >= 8
			return Chunk.new(self)
		else
			return nil
		end
	end 
	
	# Reads what.size bytes and compare the data read to what. If the comparison returns false, raises an exception, otherwise returns the data
	def readExpect (what)
		data = read(what.size)
		
		if data != what
			raise "Expected #{what} but got #{data}"
		else
			return data
		end
	end
end